package com.gitee.fanx.rpc.client;

import com.gitee.fanx.rpc.RpcCodec;
import org.springframework.http.client.ClientHttpResponse;
import org.springframework.http.converter.StringHttpMessageConverter;
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
import org.springframework.util.Assert;
import org.springframework.web.client.ResponseErrorHandler;
import org.springframework.web.client.RestTemplate;

import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;
import java.util.List;
import java.util.Optional;

/**
 * @author fanhang
 */
public class RpcClientProxyFactory {
	private final RestTemplate restTemplate;

	public RpcClientProxyFactory(List<RpcClientCustomizer> customizerList) {
		this.restTemplate = new RestTemplate(List.of(new StringHttpMessageConverter(), new MappingJackson2HttpMessageConverter(RpcCodec.JSON_MAPPER)));
		customizerList.forEach(i -> i.customize(this.restTemplate));
		this.restTemplate.setErrorHandler(new RpcResponseErrorHandler());
	}

	@SuppressWarnings("unchecked")
	public <T> T createClient(Class<T> clazz) {
		// 当前类必须为接口
		Assert.state(clazz.isInterface(), clazz + "class must be interface: [" + clazz + "]");
		RpcClient rpcClient = clazz.getAnnotation(RpcClient.class);
		Assert.notNull(rpcClient, "required @RpcClient");
		Class<?> apiClass = rpcClient.apiClass();
		String rpcClientError = "class [" + clazz + "] @RpcClient apiClass [" + apiClass.getSimpleName() + "]: ";
		// 当前接口必须继承自 apiClass
		Assert.state(apiClass.isAssignableFrom(clazz), rpcClientError + clazz.getSimpleName() + " must extends " + apiClass.getSimpleName());
		String serviceName = rpcClient.value().trim();
		Assert.hasText(serviceName, rpcClientError + "required serviceName");
		InvocationHandler invocationHandler = new RpcClientInvoker(apiClass, this.restTemplate, serviceName);
		return (T) Proxy.newProxyInstance(clazz.getClassLoader(), new Class[]{clazz}, invocationHandler);
	}

	private static class RpcResponseErrorHandler implements ResponseErrorHandler {
		@Override
		public boolean hasError(ClientHttpResponse response) throws IOException {
			return !response.getStatusCode().is2xxSuccessful();
		}
		@Override
		public void handleError(ClientHttpResponse response) throws IOException {
			try (InputStream in = response.getBody()) {
				throw RpcCodec.readException(in.readAllBytes(), response.getStatusCode().value());
			}
		}
	}
}
